package xunfei

import (
	"bufio"
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/gclient"
	"github.com/gogf/gf/v2/os/gctx"
	"github.com/gorilla/websocket"
	"io"
	"net/http"
	"net/url"
	"server/internal/dao"
	"server/internal/model/do"
	"time"
)

//讯飞语音评测
//https://www.xfyun.cn/doc/Ise/IseAPI.html

var VoiceEvaluation = &voiceEvaluation{}

type voiceEvaluation struct{}

// GetConfig 获取配置
func (v *voiceEvaluation) GetConfig() *VoiceEvaluationConfig {
	config := &VoiceEvaluationConfig{}
	res, _ := g.Cfg().Get(gctx.GetInitCtx(), "xun-fei")
	_ = res.Scan(config)
	return config
}

func (v *voiceEvaluation) getWebSocketUrl() string {
	config := v.GetConfig()

	// 固定的 URL 和 host
	baseUrl := "wss://ise-api.xfyun.cn/v2/open-ise"
	host := "ise-api.xfyun.cn"

	// 获取当前时间的 RFC1123 格式，或者使用固定时间
	date := time.Now().UTC().Format(time.RFC1123)

	// 设定 HMAC 算法和 header 信息
	algorithm := "hmac-sha256"
	headers := "host date request-line"

	// 构造签名原始字符串
	signatureOrigin := fmt.Sprintf("host: %s\ndate: %s\nGET /v2/open-ise HTTP/1.1", host, date)

	// 使用 HMAC-SHA256 进行签名
	h := hmac.New(sha256.New, []byte(config.ApiSecret))
	h.Write([]byte(signatureOrigin))
	signatureSha := h.Sum(nil)

	// 将签名结果转换为 Base64 字符串
	signature := base64.StdEncoding.EncodeToString(signatureSha)

	// 构造授权头信息
	authorizationOrigin := fmt.Sprintf(`api_key="%s", algorithm="%s", headers="%s", signature="%s"`,
		config.ApiKey, algorithm, headers, signature)

	// 对授权头信息进行 Base64 编码
	authorization := base64.StdEncoding.EncodeToString([]byte(authorizationOrigin))

	// 将参数拼接到 URL 中
	finalUrl := fmt.Sprintf("%s?authorization=%s&date=%s&host=%s", baseUrl, url.QueryEscape(authorization), url.QueryEscape(date), url.QueryEscape(host))

	return finalUrl
}

// 根据音频文件生成发送的参数
// frameData 音频文件二进制内容
// isFirstFrame 是否第一帧
// isLastFrame 是否最后一帧
func (v *voiceEvaluation) generateAudioInput(ctx context.Context, in *VoiceEvaluationExecReq, frameData []byte, isFirstFrame bool, isLastFrame bool) []byte {
	param := v.generateParams(ctx, in)
	param.Business.Cmd = "auw"
	//上传音频时来区分音频的状态（在cmd=auw即音频上传阶段为必传参数）:1：第一帧音频 2：中间的音频 4：最后一帧音频
	if isFirstFrame {
		param.Business.Aus = 1
	} else if isLastFrame {
		param.Business.Aus = 4
	} else {
		param.Business.Aus = 2
	}
	//业务数据
	param.Data.Data = base64.StdEncoding.EncodeToString(frameData)
	//发送数据的状态：第一次发送参数为0；中间数据为1；最后一次为2
	if isLastFrame {
		param.Data.Status = 2
	} else {
		param.Data.Status = 1
	}
	paramData, _ := json.Marshal(param)
	return paramData
}

// 生成参数上传阶段参数配置数据
func (v *voiceEvaluation) generateParamsInput(ctx context.Context, in *VoiceEvaluationExecReq) []byte {
	param := v.generateParams(ctx, in)
	param.Business.Cmd = "ssb"
	param.Data.Status = 0 //发送数据的状态：第一次为0；中间数据为1；最后一次为2
	paramData, _ := json.Marshal(param)
	return paramData
}

func (v *voiceEvaluation) generateParams(ctx context.Context, in *VoiceEvaluationExecReq) *VoiceEvaluationParams {
	//一：参数上传阶段，详见业务参数说明（business）：
	//	参数第一次上传，data.status=0,并设置cmd="ssb"；
	//二：音频上传阶段，此阶段开始上传音频数据：
	//	第一帧音频需要设置cmd="auw"，aus=1，data.status=1；
	//	中间帧音频需要设置cmd="auw"，aus=2，data.status=1；
	//	最后一帧音频需要设置cmd="auw"，aus=4，并设置data.status=2；

	config := v.GetConfig()

	param := VoiceEvaluationParams{}
	param.Common.AppID = config.AppID
	param.Business.Sub = "ise"                //服务类型指定 ise(开放评测)
	param.Business.Ent = "cn_vip"             //中文：cn_vip 英文：en_vip
	param.Business.Category = "read_sentence" //英文题型：read_word（词语朗读）、read_sentence（句子朗读）、read_chapter(篇章朗读)、simple_expression（英文情景反应）、read_choice（英文选择题）、topic（英文自由题）、retell（英文复述题）、picture_talk（英文看图说话）、oral_translation（英文口头翻译）
	param.Business.Aus = 1                    //上传音频时来区分音频的状态（在cmd=auw即音频上传阶段为必传参数）:1：第一帧音频 2：中间的音频 4：最后一帧音频
	param.Business.Cmd = "ssb"                //用于区分数据上传阶段: ssb：参数上传阶段 ttp：文本上传阶段（ttp_skip=true时该阶段可以跳过，直接使用text字段中的文本） auw：音频上传阶段
	//param.Business.Text = "\uFEFF" + "欢迎使用科大讯飞语音能力，让我们一起用人工智能改变世界" //待评测文本 utf8 编码，需要加utf8bom 头 TODO: 待评测文本内容
	param.Business.Text = in.Text
	param.Business.Tte = "utf-8"                    //待评测文本编码: utf-8  gbk
	param.Business.TtpSkip = true                   //跳过ttp直接使用ssb中的文本进行评测（使用时结合cmd参数查看）,默认值true
	param.Business.ExtraAbility = "multi_dimension" //extra_ability值为multi_dimension-字词句篇均适用
	param.Business.Aue = "lame"                     //音频格式:raw: 未压缩的pcm格式音频或wav（如果用wav格式音频，建议去掉头部）；lame: mp3格式音频；speex-wb;7: 讯飞定制speex格式音频(默认值)
	//param.Business.Auf = "audio/L16;rate=16000"                    //音频采样率:默认 audio/L16;rate=16000
	param.Business.Rstcd = "utf8"  //返回结果格式 utf8；gbk （默认值）
	param.Business.Group = "adult" //针对群体不同，相同试卷音频评分结果不同 （仅中文字、词、句、篇章题型支持），此参数会影响准确度得分:adult（成人群体，不设置群体参数时默认为成人） ；youth（中学群体）；pupil（小学群体，中文句、篇题型设置此参数值会有accuracy_score得分的返回）
	param.Business.CheckType = "common"
	param.Business.Rst = "plain"  //评测返回结果与分制控制（评测返回结果与分制控制也会受到ise_unite与plev参数的影响）: 完整：entirety（默认值）；精简：plain（评测返回结果将只有总分）；
	param.Business.IseUnite = "1" //英文百分制推荐传参（rst="entirety"且ise_unite="1"且配合extra_ability参数使用）
	param.Business.Plev = "0"     //在rst="entirety"（默认值）且ise_unite="0"（默认值）的情况下plev的取值不同对返回结果有影响。 plev：0(英文包含accuracy_score、serr_msg、 syll_accent、fluency_score、standard_score、pitch信息的返回)
	return &param
}

func (v *voiceEvaluation) Exec(in *VoiceEvaluationExecReq) (finalResult *VoiceEvaluationFinalResult, err error) {
	ctx := gctx.New()
	client := gclient.NewWebSocket()
	client.HandshakeTimeout = time.Second * 10 // 设置超时时间
	conn, _, err := client.Dial(v.getWebSocketUrl(), nil)
	if err != nil {
		return nil, err
	}

	defer func() {
		_ = conn.Close()
		errorMessage := ""
		if err != nil {
			g.Log().Error(ctx, err)
			errorMessage = err.Error()
		}
		//记录的数据
		g.Log().Info(ctx, finalResult)
		_, _ = dao.LessonVoiceEvaluationRecord.Ctx(ctx).Data(do.LessonVoiceEvaluationRecord{
			Sn:           in.Sn,
			FileUrl:      in.Filepath,
			TotalScore:   finalResult.TotalScore.Value,
			ErrorMessage: errorMessage,
		}).InsertAndGetId()
	}()

	// 参数上传阶段
	err = conn.WriteMessage(websocket.TextMessage, v.generateParamsInput(ctx, in))
	if err != nil {
		return
	}

	// 音频上传阶段
	// 打开本地文件循环读取
	//file, err := os.Open(in.Filepath)
	//if err != nil {
	//	panic(err.Error())
	//}
	//defer func() {
	//	_ = file.Close()
	//}()

	// 从远程服务器打开文件并循环读取
	resp, err := http.Get(in.Filepath) // 假设 in.Filepath 是远程文件的 URL
	if err != nil {
		return
	}
	defer func() {
		_ = resp.Body.Close()
	}()

	chunkSize := 1280
	buffer := make([]byte, chunkSize)
	reader := bufio.NewReader(resp.Body) //远程文件：resp.Body  本地文件 file
	isFirstFrame := true
	isLastFrame := false
	for {
		_, err = reader.Read(buffer)

		//每次发送音频间隔40ms，每次发送音频字节数1280B,控制频率，否则会直接60114错误
		//官方建议40ms，实测可能会波动，待实测
		time.Sleep(200 * time.Millisecond)

		//发送最后一批数据
		if err == io.EOF {
			isLastFrame = true
			err = conn.WriteMessage(websocket.TextMessage, v.generateAudioInput(ctx, in, buffer, isFirstFrame, isLastFrame))
			if err != nil {
				return nil, err
			}
			break
		}
		if err != nil {
			break
		}
		err = conn.WriteMessage(websocket.TextMessage, v.generateAudioInput(ctx, in, buffer, isFirstFrame, isLastFrame))
		if err != nil {
			return nil, err
		}
		isFirstFrame = false
	}

	// 循环等待结果
	for {
		_, tempResult, err := conn.ReadMessage()
		g.Log().Info(ctx, tempResult)
		if err != nil {
			return nil, err
		}
		finalResult, _ = v.handleMessage(ctx, tempResult)
		if finalResult != nil {
			return finalResult, nil
		}
	}
}

// handleMessage 检测websocket中的最后一条消息（结果）后返回
func (v *voiceEvaluation) handleMessage(ctx context.Context, result []byte) (finalResult *VoiceEvaluationFinalResult, err error) {
	defer func() {
		if err != nil {
			g.Log().Error(ctx, err)
		}
	}()
	resData := VoiceEvaluationResponse{}
	err = json.Unmarshal(result, &resData)
	if err != nil {
		return nil, err
	}
	//有效的评测结果返回
	if resData.Data.Status == 2 {
		VoiceEvaluationResult, err := base64.StdEncoding.DecodeString(resData.Data.Data)
		if err != nil {
			return nil, err
		}
		finalResult = new(VoiceEvaluationFinalResult)
		err = xml.Unmarshal(VoiceEvaluationResult, &finalResult)
		if err != nil {
			return nil, err
		}
		return finalResult, err //获取到数据再返回
	}
	return
}
